package org.vaadin.vaadinjit.widgetset.client.ui;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;

import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.Element;

import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;

/**
 * This is the base class for all client side visualizations. It handles calling
 * the initialization and common properties 
 * 
 * @author jonkka
 *
 * @param <T> The overlay class used by the visualization
 */
public abstract class VJitWidget<T extends JitOverlay> 
								extends Widget implements Paintable {

	
	/** Set the CSS class name to allow styling. */
	public static final String CLASSNAME = "v-jitwrapper";
	
	private static final int MIN_WIDTH = 100;
	
	private static final int MIN_HEIGHT = 100;
	
	private static final String DEFAULT_BACKGROUND_COLOR = "#1a1a1a";
	
	protected static final String DEFAULT_TEXT_COLOR = "#ffffff";
	
	/** 
	 * This is used to make unique id:s for every element containing
	 * an infovis component. This is needed to show multiple infovis
	 * components in the same DOM. The reason is that the visualization
	 * is injected based on the id of the element.
	 */
	private static int count = 0;
	
	/** The client side widget identifier */
	protected String paintableId;

	/** Reference to the server connection object. */
	protected ApplicationConnection client;

	/** Tells if the infovis visualization has been initied */
	private boolean isInited = false;
	
	/** The visualization */
	private T visualization;
	
	/** The unique id of this visualization */
	private String elementId;

	/** The DOM element into which JIT injects the visualization */
	private final Element wrapper;
	
	/** The JSON data for the visualization */
	private JSONObject jsonData = null;
		
	/** The background color of the visualization */
	private String backgroundColor = DEFAULT_BACKGROUND_COLOR;

	private String textColor = DEFAULT_TEXT_COLOR;
	
	/** Contains the configuration for the visualization */
	private JitConfiguration jitConfig;
	
	private boolean animate = true;
	
	private boolean useGradients = true;
	
	private String labelType = "Native";
	
	private boolean newData = false;
	
	/**
	 * The constructor should first call super() to initialize the component and
	 * then handle any initialization relevant to Vaadin.
	 */
	public VJitWidget() {

		// Set the element id to a unique value
		elementId = "infovis-"+count;
		
		// Increase the count every time a viz is created
		count++;
		
		// Create a new div element and set it as root element for the widget
		setElement(DOM.createDiv());
		
		// Create a new div element
		wrapper = DOM.createDiv();
		
		// Add the newly created div element as a child to the root element
		DOM.appendChild(getElement(), wrapper);
		
		// Set the id property of the element, this is how JIT knows where
		// to inject the visualization.
		DOM.setElementProperty(wrapper, "id", elementId);

		setHeight(MIN_HEIGHT+"px");
		setWidth(MIN_WIDTH+"px");
		DOM.setStyleAttribute(wrapper, "backgroundColor", backgroundColor);
		
		// This method call of the Paintable interface sets the component
		// style name in DOM tree
		setStylePrimaryName(CLASSNAME);

		// Register the widget with JitWrapper to receive call-backs from JIT
		JitWrapper.registerJitWidget(this);
				
		// Execute JIT's native initialization
		setUpJit();
		
		// Initialize the configuration object
		jitConfig = new JitConfiguration();
		
		// Setup the configuration object
	}

	/**
	 * This is an identifier used be {@link JitWrapper} and JIT itself to identify the 
	 * visualization when doing call-backs and to tell JIT which div to inject the
	 * visualization in. the identifier is unique to each visualization running in a 
	 * Vaadin application.
	 * 
	 * @return the unique identifier for this visualization
	 */
	public String getJitIdentifier() {
		return elementId;
	}
	
	
	/**
	 * Get the JSON data that the visualization is showing.
	 * 
	 * @return a JSON object containing the visualization data.
	 */
	public JSONObject getJitData() {
		return jsonData;
	}
	
	
	/**
	 * Set the background color of the visualization.
	 * 
	 * @param color the background color in CSS compatible format
	 */
	public void setBackgroundColor(String color) {
		this.backgroundColor = color;
		DOM.setStyleAttribute(wrapper, "backgroundColor", backgroundColor);
	}
	
	
	/**
	 * Get the background color of the visualization.
	 * 
	 * @return the background color in hex format
	 */
	public String getBackgroundColor() {
		return backgroundColor;
	}
	
	
	/**
	 * Set the text color of the visualization. This is used for instance
	 * to set the color of the labels in the visualization
	 * 
	 * @param color the color in CSS compatible format
	 */
	public void setTextColor(String color) {
		this.textColor = color;
	}
	
	
	/**
	 * Get the text color of the visualization (used for labels etc.)
	 * 
	 * @return the color
	 */
	public String getTextColor() {
		return textColor;
	}
	
	
	/**
	 * Called whenever an update is received from the server 
	 */
	public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
		
		// This call should be made first. 
		// It handles sizes, captions, tooltips, etc. automatically.
		if (client.updateComponent(this, uidl, true)) {
			// If client.updateComponent returns true there has been no changes and we
			// do not need to update anything.
			return;
		}
		
		if (uidl.hasAttribute("height")) {
			String height = uidl.getStringAttribute("height");
			this.setHeight(height);
		}
		if (uidl.hasAttribute("width")) {
			String width = uidl.getStringAttribute("width");
			this.setWidth(width);
		}
		if (uidl.hasAttribute("backgroundColor")) {
			String bgColor = uidl.getStringAttribute("backgroundColor");
			setBackgroundColor(bgColor);
		}
		if (uidl.hasAttribute("textColor")) {
			String textColor = uidl.getStringAttribute("textColor");
			setTextColor(textColor);
		}
		
		// Save reference to server connection object to be able to send
		// user interaction later
		this.client = client;

		// Save the client side identifier (paintable id) for the widget
		paintableId = uidl.getId();

		
		// Check if JIT should be initialized
		if (!isInited) {
			
			UIDL initUidl = uidl.getChildByTagName("init");
			if (initUidl != null) {
				initialize(initUidl);
				
				// Set up default values
				setupConfig(jitConfig);
				// Receive user set configuration
				UIDL configUidl = uidl.getChildByTagName("config");
				if (configUidl != null) {
					handleUserConfigurationProperties(configUidl, jitConfig);
				}
				
				visualization = initJit(jitConfig.getNativeConfig());
				
				isInited = true;
			}
		}

		
		// Check if the UIDL contains data for the chart
		if (uidl.hasAttribute("jitData")) {
			if (uidl.hasAttribute("newData")) {
				newData = uidl.getBooleanAttribute("newData");
			}
			parseDataToJSON(uidl.getStringAttribute("jitData"));
		}
	}



	
	private void handleUserConfigurationProperties(UIDL configUidl, JitConfiguration jitConfig) {
		String[] optionNames = configUidl.getStringArrayAttribute("optionNames");
		String[] optionTypes = configUidl.getStringArrayAttribute("optionTypes");
		
		if (optionNames == null || optionTypes == null)
			throw new RuntimeException("optionNames and optionValues must be non-null");
		
		int i = 0;
		for (String optName : optionNames) {
			if (!configUidl.hasAttribute(optName))
				throw new RuntimeException("No value exists for option " + optName);
			
			String optType = optionTypes[i++];
			String[] optPath = optName.split(":");

			if (optPath.length > 1) {
				if (optType.equals("string")) {
					String value = configUidl.getStringAttribute(optName);
					jitConfig.setSubOption(optPath, value);
				}
				else if (optType.equals("boolean")) {
					Boolean value = configUidl.getBooleanAttribute(optName);
					jitConfig.setSubOption(optPath, value);
				}
				else if (optType.equals("double")) {
					Double value = configUidl.getDoubleAttribute(optName);
					jitConfig.setSubOption(optPath, value);
				}
				else if (optType.equals("int")) {
					Integer value = configUidl.getIntAttribute(optName);
					jitConfig.setSubOption(optPath, value);
				}
				else {
					throw new RuntimeException("Unsupported type in configUidl");
				}

			}
			else {
				if (optType.equals("string")) {
					String value = configUidl.getStringAttribute(optName);
					jitConfig.setOption(optName, value);
				}
				else if (optType.equals("boolean")) {
					Boolean value = configUidl.getBooleanAttribute(optName);
					jitConfig.setOption(optName, value);
				}
				else if (optType.equals("double")) {
					Double value = configUidl.getDoubleAttribute(optName);
					jitConfig.setOption(optName, value);
				}
				else if (optType.equals("int")) {
					Integer value = configUidl.getIntAttribute(optName);
					jitConfig.setOption(optName, value);
				}
				else {
					throw new RuntimeException("Unsupported type in configUidl");
				}
			}
		}
		
	}

	@Override
	public void setHeight(String height) {
		// Let GWT do what it needs to
		super.setHeight(height);
				
		DOM.setStyleAttribute(wrapper, "height", height);		
		
		// If the visualization already is initialized and the width is given in pixels,
		// we will resize the native visualization.
		if (isInited && height.endsWith("px")) {
			
			int newHeight = Integer.parseInt(height.substring(0, height.length()-2));
			
			Integer[] currentSize = visualization.getCanvas().getSize();
		
			// Only resize if the size has changed
			if (newHeight != currentSize[JitCanvasOverlay.HEIGHT]) {
				visualization.getCanvas().resize(
						currentSize[JitCanvasOverlay.WIDTH], newHeight);
			}
		}

	}
	
	
	@Override
	public void setWidth(String width) {
		// Let GWT do what it needs to
		super.setWidth(width);
		
		// Resize our wrapper div element
		DOM.setStyleAttribute(wrapper, "width", width);
		
		// If the visualization already is initialized and the width is given in pixels,
		// we will resize the native visualization.
		if (isInited && width.endsWith("px")) {
			
			int newWidth = Integer.parseInt(width.substring(0, width.length()-2));
			
			Integer[] currentSize = visualization.getCanvas().getSize();
		
			// Only resize if the size has changed
			if (newWidth != currentSize[JitCanvasOverlay.WIDTH]) {
				visualization.getCanvas().resize(
						newWidth, currentSize[JitCanvasOverlay.HEIGHT]);
			}
		}
	}

	
	/**
	 * Get the overlay of the native visualization
	 * 
	 * @return the overlay of the native visualization.
	 */
	protected T getVisualization() {
		return visualization;
	}
	
	
	/**
	 * Tells if the native visualization has been initialized.
	 * 
	 * @return true if the native visualization has been initialized
	 */
	protected boolean isInitialized() {
		return isInited;
	}
	
	/**
	 * <p>
	 * This method should be implemented to call the JIT library to create the 
	 * visualization.
	 * </p>
	 * 
	 * <p>
	 * Implementations should not call this method directly since the base class
	 * takes care of calling the method when needed, but if they do they should make
	 * sure to call the {@link #setUp} method before.
	 * </p>
	 * 
	 * @param elName The id of the div that the visualization is going to be
	 * 			injected to.
	 * @return The overlay class of the initialized visualization.
	 */
	protected abstract T initJit(JavaScriptObject config);
	//protected abstract T initJit(String elName);
	
	/**
	 * Loads the JSON data. This method assumes that the data is correct.
	 * 
	 * @param data The json data to load
	 */
	protected abstract void loadJitData(JSONObject data);
	
	
	/**
	 * Handles the initialization UIDL
	 * 
	 * @param initUidl the initialization UIDL object
	 */
	protected void initialize(UIDL initUidl) {}
	
	
	/**
	 * This method is responsible for doing necessary set up for JIT visualizations,
	 * such as taking care of cross-browser compatibility. This method should always
	 * be called before <code>initJit</code>
	 * 
	 */
	protected native void setUpJit() /*-{
		var ua = navigator.userAgent,
		iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
		typeOfCanvas = typeof HTMLCanvasElement,
		nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
		textSupport = nativeCanvasSupport 
        	&& (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
  
  		//I'm setting this based on the fact that ExCanvas provides text support for IE
  		//and that as of today iPhone/iPad current text support is lame
  		labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML';
  		nativeTextSupport = labelType == 'Native';
  		useGradients = nativeCanvasSupport;
  		animate = !(iStuff || !nativeCanvasSupport);
  		
  		this.@org.vaadin.vaadinjit.widgetset.client.ui.VJitWidget::animate = animate;
  		this.@org.vaadin.vaadinjit.widgetset.client.ui.VJitWidget::useGradients = useGradients;
  		this.@org.vaadin.vaadinjit.widgetset.client.ui.VJitWidget::labelType = labelType;

	}-*/;
	
	
	/**
	 * 
	 * @param config
	 */
	protected void setupConfig(JitConfiguration config) {
		config.setOption("injectInto", elementId);
		config.setOption("animate", animate);
	}
	
	protected boolean getAnimate() {
		return animate;
	}
	
	protected boolean getUseGradients() {
		return useGradients;
	}
	
	protected String getLabelType() {
		return labelType;
	}
	
	protected boolean getNewDataLoaded() {
		return newData;
	}
	
	protected void setNewDataLoaded(boolean value) {
		newData = value;
	}
	
	/**
	 * Parses the string representation of the JSON data to a JSONObject.
	 * 
	 * @param jsonString
	 */
	private void parseDataToJSON(String jsonString) {
		JSONObject json = JSONParser.parse(jsonString).isObject();
		// TODO Should check that the data is valid
		if (json == null) {
			GWT.log("Invalid data passed to client-side widget: " + jsonString);
			return;
		}
		GWT.log(json.toString());
		
		jsonData = json;
		loadJitData(jsonData);
	}
	
}